package multidraw.model.server;

import java.awt.Color;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

import multidraw.model.Canvas;
import multidraw.model.CentralCanvasObserver;
import multidraw.model.Shape;
import multidraw.model.TextBox;
import multidraw.model.commands.Command;
import multidraw.tools.SelectorTool;
import multidraw.tools.Tool;

/**
 
 * @author Cuong Bui.
 * @version 1.0.
 * @note Server object which contains 
 * the central model (data) of the whole system
 * @composed - - - CoreData 
 */
public class CanvasServer extends UnicastRemoteObject implements CanvasModel {
	//CoreData coreData;
	//List<CentralCanvasObserver> observers = new ArrayList<CentralCanvasObserver>();
	List<String> userList = new ArrayList<String>();
	List<String> activeUserList = new ArrayList<String>();
	
	/**
	 * The table of clients implemented as a map from clientID to client 
	 * details information.
	 */
	Hashtable<String, ClientInfo> clients = 
		new Hashtable<String, ClientInfo>();
	
	/**
	 * The table which stores all the information about sessions currently hosted
	 * by this server.
	 */
	Hashtable<String, SessionInfo> sessions = 
		new Hashtable<String, SessionInfo>();
	
	
	private Tool currentTool;
	private Color penColor = Color.black;
	
	//Shape selectedObject = null;
	int selectedObjectIndex = -1;
	
	protected SecureRandom rand = new SecureRandom();
	
	public CanvasServer() throws RemoteException  {
		//coreData = new CoreData();		
		super(1099);
		try {
			//Bind the remote object's stub in the registry
			Registry registry = LocateRegistry.createRegistry(1099);
			registry.rebind("CanvasModel", this);
		} catch (Exception e) {
			System.err.println("Server exception: " + e.toString());
			e.printStackTrace();
		}
	}
	
//	public Shape getSelectedObject() {
//		if (selectedObjectIndex >= 0 && selectedObjectIndex < coreData.shapes.size())
//			return coreData.shapes.get(selectedObjectIndex);
//		else return null;
//	}
	
	public int getSelectedObjectIndex() {
		return selectedObjectIndex;
	}

	/*
	 * We need to add something to allow for a unique login process
	 * At this point the user will submit their login credentials
	 * It will identify if that name already exists or not
	 * For now this will be performed inside attach Observer
	 * eventually it will need to be moved outside to its own method
	 */
	
	public int attachObserver(CentralCanvasObserver o) throws RemoteException {
		
		/*
		 * This step allows a user that is logged in to connect to an existing session
		 * If the user is creating a session it will automatically attach that user
		 * to the created session and make them the owner
		 */
		
		String username = o.getName();
		if (username == null) return -2; //non-existed name
		
		if (clients.get(username) != null) return -1; //existed name
		
		ClientInfo clientInfo = new ClientInfo();
		clientInfo.clientID = o.getName();
		clientInfo.clientObject = o;
		clientInfo.updates = new ArrayList<Command>();
		clients.put(o.getName(), clientInfo);
		
		//observers.add(o);
		return 0;
	}
	
	public void notifyObservers() {
		for (ClientInfo o: clients.values()) {
			try {
				o.clientObject.updateModelContent();
			} catch (RemoteException e) {
				System.err.println(e);
			}
		}
	}
	
//	public List<Shape> getData() {
//		return coreData.shapes;
//	}
	
//	public void addShape(Shape s) {
//		coreData.shapes.add(s);
//		notifyObservers();
//	}
	
//	public void selectObject(int X, int Y) {
//		for (int i = coreData.shapes.size() - 1; i >= 0; i--) {
//			Shape s = coreData.shapes.get(i);
//			
//			if (s.isInsideSelectionalArea(X, Y)) {
//				//selectedObject = s;
//				selectedObjectIndex = i;
//				//JOptionPane.showMessageDialog(null, "Selected");
//				notifyObservers();
//				return;
//			}
//		}
//		
//		selectedObjectIndex = -1; //no object selected
//		//JOptionPane.showMessageDialog(null, "None Selected");
//		notifyObservers();
//	}
	
	public void setCurrentTool(Tool t) {
		currentTool = t;
		if (!(currentTool instanceof SelectorTool)) {
			selectedObjectIndex = -1; //deselect all objects.
		}
		notifyObservers();
	}
	
	public Tool getCurrentTool() {
		return currentTool;
	}
	
//	public void clearCanvas(String clientID, String sessionID) {
//		SessionInfo sessionInfo = sessions.get(sessionID);
//		
//		if (sessionInfo != null)
//			
//		coreData.shapes.clear();
//		//fixed the null pointer exception in getSelectedObject.
//		selectedObjectIndex = -1;
//		//update all View
//		notifyObservers();
//	}
	
	//public void fillShape
//	public Color getPenColor() {
//		return penColor;
//	}
	
//	public void setPenColor(Color c) {
//		System.out.println("Canvas color change ...");
//		System.out.println("color = " + c);
//		penColor = c;
//		notifyObservers();
//	}
	
//	public void deleteSelectedObject() {
//		if (selectedObjectIndex >= 0) {
//			coreData.shapes.remove(selectedObjectIndex);
//			selectedObjectIndex = -1;
//		}
//		notifyObservers();
//	}
	
	/**
	 * Switch filling option of currently selected object
	 */
//	public void switchSelectedObjectFilling(){
//		if (getSelectedObject() != null) {
//		    getSelectedObject().switchFilling();
//		    notifyObservers();
//		}
//	}
	
	public static void main(String[] args) {
		try {
			//Server obj = new Server();
			//Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 1100);
			
			CanvasServer obj = new CanvasServer();
			
			System.err.println("Server ready");			
		} catch (Exception e) {
			System.err.println("Server exception: " + e.toString());
			e.printStackTrace();
		}
	}

	@Override
	public void detachObserver(CentralCanvasObserver o) throws RemoteException {
		// TODO Auto-generated method stub
		
	}
	
	@Override
	public void applyCommand(Command command) throws RemoteException {
		String sessionID = command.getSessionID();
		SessionInfo sessionInfo = sessions.get(sessionID);
		if (sessionInfo != null) {
			sessionInfo.applyCommand(command);
		}
	}
	
//	public void applyAddShape(Shape s) {
//		coreData.shapes.add(s);
//	}
	
	public List<Command> getUpdates(String clientID) {
		ClientInfo clientInfo = clients.get(clientID);
		if (clientInfo != null) {
			ArrayList<Command> updates = new ArrayList<Command>(clientInfo.updates);
			clientInfo.updates.clear();
			return updates;
		} else return null;
		
	}
	
	public CoreData getCoreData(String sessionID) {
		SessionInfo sessionInfo = sessions.get(sessionID);
		
		if (sessionInfo != null) {
			CoreData centralCoreData = sessionInfo.coreData;
			CoreData tmpCoreData = new CoreData();
			tmpCoreData.shapes = new ArrayList<Shape>(centralCoreData.shapes);
			tmpCoreData.revisionID = centralCoreData.revisionID;
			return tmpCoreData;
		}
		
		return null;
	}
	
	public void applyCommandToCoreData(Command c) {
		
	}
	
	public long getNextID(String sessionID) {
		SessionInfo sessionInfo = sessions.get(sessionID);
		
		if (sessionInfo != null) {
			return sessionInfo.getNextID();
		}
		return -1;
	}
	
//	public void applyAddChar(long shapeID, char ch) {
//		
//	}
	
//	public List<Shape> getShapes() {
//		return coreData.shapes;
//	}

	public void setSelectedObjectIndex(int index) {
		//do nothing, because the server don't maintain this concept.
	}

	
	
	public String createSession(String clientID) {
		//check if this clientID is ok
		ClientInfo clientInfo = clients.get(clientID);
		if (clientInfo == null) return null;
		
		//generate a random sessionID
		String sessionID = null;
		byte[] buff = new byte[8];
		rand.nextBytes(buff);
		sessionID = getHex(buff);
		
		//create a session object
		SessionInfo sessionInfo = new SessionInfo(sessionID, this);
		sessionInfo.userRoles.put(clientID, Role.OWNER);
		
		sessions.put(sessionID, sessionInfo);
		
		clientInfo.sessionID = sessionID;
		
		System.err.println(sessionID);
		
		return sessionID;
	}
	
	static final String HEXES = "0123456789ABCDEF";

	public static String getHex(byte[] raw) {
		if (raw == null) {
			return null;
		}
		final StringBuilder hex = new StringBuilder(2 * raw.length);
		for (final byte b : raw) {
			hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(
					HEXES.charAt((b & 0x0F)));
		}
		return hex.toString();
	}
	
	public ClientInfo getClientInfo(String clientID) {
		return clients.get(clientID);
	}
	
	public String joinSession(String clientID, String ownerID) throws RemoteException {
		ClientInfo clientInfo = clients.get(ownerID);
		
		if (clientInfo == null) throw new RemoteException("Cannot find user");
		
		if (clientInfo.sessionID == null) throw new RemoteException("Cannot find this user's session");
		
		//check if this user the owner
		String sessionID = clientInfo.sessionID;
		
		SessionInfo sessionInfo = sessions.get(sessionID);
		
		if (sessionInfo == null) throw new RemoteException("Cannot join the session");
		
		sessionInfo.joinClient(clientID);
		return sessionID;
	}

	@Override
	public Vector<String> getClients(String sessionID) throws RemoteException {
		// TODO Auto-generated method stub
		
		SessionInfo sessionInfo = sessions.get(sessionID);
		
		if (sessionInfo == null) throw new RemoteException("Cannot find the session");
		
		Vector<String> clients = new Vector<String>();
		
		for (String n : sessionInfo.userRoles.keySet()) {
			clients.add(n);
		}
		return clients;
	}

	@Override
	public Hashtable<String, Role> getUserRoles(String sessionID)
			throws RemoteException {
		// TODO Auto-generated method stub
		SessionInfo sessionInfo = sessions.get(sessionID);
		
		if (sessionInfo == null) throw new RemoteException("Cannot find the session");
		
		return sessionInfo.userRoles;
	}
}
